/**
 * The QQ2003C protocol plugin
 *
 * for gaim
 *
 * Copyright (C) 2004 Puzzlebird
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * auint32_t with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 *
 * OICQ encryption algorithm
 * Convert from ASM code provided by PerlOICQ
 * 
 * Puzzlebird, Nov-Dec 2002
 */

// START OF FILE
/*****************************************************************************/
/*Notes: (OICQ uses 0x10 iterations, and modified something...)
 
IN : 64  bits of data in v[0] - v[1].
OUT: 64  bits of data in w[0] - w[1].
KEY: 128 bits of key  in k[0] - k[3].
 
delta is chosen to be the real part of 
the golden ratio: Sqrt(5/4) - 1/2 ~ 0.618034 multiplied by 2^32. 
 
0x61C88647 is what we can track on the ASM codes.!!
*/

#ifndef _WIN32
#include <arpa/inet.h>
#else
#include "win32dep.h"
#endif

#include <string.h>

#include "crypt.h"

/*****************************************************************************/
void qq_encipher(uint32_t *const v, const uint32_t *const k, uint32_t *const w) {
    register uint32_t y = ntohl(v[0]), z = ntohl(v[1]), a = ntohl(k[0]), b = ntohl(k[1]), c = ntohl(k[2]), d = ntohl(k[3]), n = 0x10, sum = 0, delta = 0x9E3779B9;	/*  0x9E3779B9 - 0x100000000 = -0x61C88647 */

    while (n-- > 0) {
        sum += delta;
        y += ((z << 4) + a) ^ (z + sum) ^ ((z >> 5) + b);
        z += ((y << 4) + c) ^ (y + sum) ^ ((y >> 5) + d);
    }			// while

    w[0] = htonl(y);
    w[1] = htonl(z);
}				// qq_enciper

/*****************************************************************************/
void qq_decipher(uint32_t *const v, const uint32_t *const k, uint32_t *const w) {
    register uint32_t y = ntohl(v[0]), z = ntohl(v[1]), a = ntohl(k[0]), b = ntohl(k[1]), c = ntohl(k[2]), d = ntohl(k[3]), n = 0x10, sum = 0xE3779B90,	// why this ? must be related with n value
                               delta = 0x9E3779B9;

    /* sum = delta<<5, in general sum = delta * n */
    while (n-- > 0) {
        z -= ((y << 4) + c) ^ (y + sum) ^ ((y >> 5) + d);
        y -= ((z << 4) + a) ^ (z + sum) ^ ((z >> 5) + b);
        sum -= delta;
    }

    w[0] = htonl(y);
    w[1] = htonl(z);
}				// qq_decipher

/********************************************************************
 * encrypt part
 *******************************************************************/

void qq_encrypt(unsigned char *instr, int instrlen, unsigned char *key, unsigned char *outstr, int *outstrlen_prt) {
    unsigned char plain[8],	// plain text buffer
    plain_pre_8[8],	// plain text buffer, previous 8 bytes
    *crypted,		// crypted text
    *crypted_pre_8,		// crypted test, previous 8 bytes
    *inp;			// current position in instr
    int pos_in_byte = 1,	// loop in the byte
                      is_header = 1,	// header is one byte
                                  count = 0,		// number of bytes being crypted
                                          padding = 0;	// number of padding stuff

    int rand(void) {	// it can be the real random seed function
        return 0xdead;
    }			// override with number, convenient for debug

    /*** we encrypt every eight byte ***/
    void encrypt_every_8_byte(void) {
        for (pos_in_byte = 0; pos_in_byte < 8; pos_in_byte++) {
            if (is_header) {
                plain[pos_in_byte] ^= plain_pre_8[pos_in_byte];
            } else {
                plain[pos_in_byte] ^= crypted_pre_8[pos_in_byte];
            }
        }		// prepare plain text
        qq_encipher((uint32_t *) plain, (uint32_t *) key, (uint32_t *) crypted);	// encrypt it

        for (pos_in_byte = 0; pos_in_byte < 8; pos_in_byte++) {
            crypted[pos_in_byte] ^= plain_pre_8[pos_in_byte];
        }
        memcpy(plain_pre_8, plain, 8);	// prepare next

        crypted_pre_8 = crypted;	// store position of previous 8 byte
        crypted += 8;	// prepare next output
        count += 8;	// outstrlen increase by 8
        pos_in_byte = 0;	// back to start
        is_header = 0;	// and exit header
    }			// encrypt_every_8_byte

    pos_in_byte = (instrlen + 0x0a) % 8;	// header padding decided by instrlen
    if (pos_in_byte) {
        pos_in_byte = 8 - pos_in_byte;
    }
    plain[0] = (rand() & 0xf8) | pos_in_byte;

    memset(plain + 1, rand() & 0xff, pos_in_byte++);
    memset(plain_pre_8, 0x00, sizeof(plain_pre_8));

    crypted = crypted_pre_8 = outstr;

    padding = 1;		// pad some stuff in header
    while (padding <= 2) {	// at most two byte
        if (pos_in_byte < 8) {
            plain[pos_in_byte++] = rand() & 0xff;
            padding++;
        }
        if (pos_in_byte == 8) {
            encrypt_every_8_byte();
        }
    }

    inp = instr;
    while (instrlen > 0) {
        if (pos_in_byte < 8) {
            plain[pos_in_byte++] = *(inp++);
            instrlen--;
        }
        if (pos_in_byte == 8) {
            encrypt_every_8_byte();
        }
    }

    padding = 1;		// pad some stuff in tailer
    while (padding <= 7) {	// at most sever byte
        if (pos_in_byte < 8) {
            plain[pos_in_byte++] = 0x00;
            padding++;
        }
        if (pos_in_byte == 8) {
            encrypt_every_8_byte();
        }
    }

    *outstrlen_prt = count;
}				// qq_encrypt


/********************************************************************
 * [decrypt part]
 * return 0 if failed, otherwise return 1
 ********************************************************************/

int qq_decrypt(unsigned char *instr, int instrlen, unsigned char *key, unsigned char *outstr, int *outstrlen_ptr) {
    unsigned char decrypted[8], m[8], *crypt_buff, *crypt_buff_pre_8, *outp;
    int count, context_start, pos_in_byte, padding;

    int decrypt_every_8_byte(void) {
        for (pos_in_byte = 0; pos_in_byte < 8; pos_in_byte++) {
            if (context_start + pos_in_byte >= instrlen)
                return 1;
            decrypted[pos_in_byte] ^= crypt_buff[pos_in_byte];
        }
        qq_decipher((uint32_t *) decrypted, (uint32_t *) key, (uint32_t *) decrypted);

        context_start += 8;
        crypt_buff += 8;
        pos_in_byte = 0;
        return 1;
    }			// decrypt_every_8_byte

    // at least 16 bytes and %8 == 0
    if ((instrlen % 8) || (instrlen < 16))
        return 0;
    // get information from header
    qq_decipher((uint32_t *) instr, (uint32_t *) key, (uint32_t *) decrypted);
    pos_in_byte = decrypted[0] & 0x7;
    count = instrlen - pos_in_byte - 10;	// this is the plaintext length
    // return if outstr buffer is not large enought or error plaintext length
    if (*outstrlen_ptr < count || count < 0)
        return 0;

    memset(m, 0, 8);
    crypt_buff_pre_8 = m;
    *outstrlen_ptr = count;	// everything is ok! set return string length

    crypt_buff = instr + 8;	// address of real data start
    context_start = 8;	// context is at the second 8 byte
    pos_in_byte++;		// start of paddng stuff

    padding = 1;		// at least one in header
    while (padding <= 2) {	// there are 2 byte padding stuff in header
        if (pos_in_byte < 8) {	// bypass the padding stuff, none sense data
            pos_in_byte++;
            padding++;
        }
        if (pos_in_byte == 8) {
            crypt_buff_pre_8 = instr;
            if (!decrypt_every_8_byte())
                return 0;
        }
    }			// while

    outp = outstr;
    while (count != 0) {
        if (pos_in_byte < 8) {
            *outp = crypt_buff_pre_8[pos_in_byte] ^ decrypted[pos_in_byte];
            outp++;
            count--;
            pos_in_byte++;
        }
        if (pos_in_byte == 8) {
            crypt_buff_pre_8 = crypt_buff - 8;
            if (!decrypt_every_8_byte())
                return 0;
        }
    }			// while

    for (padding = 1; padding < 8; padding++) {
        if (pos_in_byte < 8) {
            if (crypt_buff_pre_8[pos_in_byte] ^ decrypted[pos_in_byte])
                return 0;
            pos_in_byte++;
        }
        if (pos_in_byte == 8) {
            crypt_buff_pre_8 = crypt_buff;
            if (!decrypt_every_8_byte())
                return 0;
        }
    }			// for
    return 1;
}				// qq_decrypt

/*****************************************************************************/
/* This is the Public Function */
// return 1 is succeed, otherwise return 0
int qq_crypt(unsigned char flag,
             unsigned char *instr, int instrlen, unsigned char *key, unsigned char *outstr, int *outstrlen_ptr) {
    if (flag == DECRYPT)
        return qq_decrypt(instr, instrlen, key, outstr, outstrlen_ptr);
    else if (flag == ENCRYPT)
        qq_encrypt(instr, instrlen, key, outstr, outstrlen_ptr);

    return 1;		// flag must be DECRYPT or ENCRYPT
}				// qq_crypt

/*****************************************************************************/
// END OF FILE
